/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package ncsutest.robowulf.sensors;

import java.io.IOException;

import com.sun.spot.resourcesharing.ResourceUnavailableException;
import com.sun.spot.sensorboard.io.IScalarInput;
import com.sun.spot.sensorboard.io.IScalarInputThresholdListener;

/**
 *
 * @author David A. Boyuka, II
 */
public class IRSensor implements IScalarInputThresholdListener {
	// Note: perfect white is 0, and perfect black is 1024.
	public static final int BLACK_THRESHOLD = 700;
    public  static final int WHITE_THRESHOLD = 300;
    private static final int THRESHOLD = (BLACK_THRESHOLD + WHITE_THRESHOLD) / 2;

	private static final int MIN_VALUE = 0;
	private static final int MAX_VALUE = 1023;
	
   	private String name;
    private IScalarInput pin;
    private int threshLow, threshHigh;
	
    private int counter = 0; // Access to counter should be synchronized using "this" as a mutex
    private boolean isHigh;
    
    private IRSensorListener[] listeners = new IRSensorListener[1];
    private int listenerSize = 0;

    //
    // Constructor
    //

    public IRSensor(String name, IScalarInput pin) {
    	this.name = name;
    	this.pin = pin;
    	
    	resetCounter();
        pin.addIScalarInputThresholdListener(this);
    }
    
    public void destroy() {
    	pin.removeIScalarInputThresholdListener(this);
    }
    
    //
    // General public methods
    //
    
    public void resetCounter() {
    	synchronized (this) {
    		counter = 0;
    		isHigh = isPinHigh();
    		setThresholds();
    	}
    }   

    //
    // IRSensorListener methods
    //

    public void addIRSensorListener(IRSensorListener listener) {
    	synchronized (listeners) {
    		if (listenerSize == listeners.length) {
    			IRSensorListener[] newListeners = new IRSensorListener[listeners.length*2];
    			System.arraycopy(listeners, 0, newListeners, 0, listeners.length);
    		}
    		listeners[listenerSize++] = listener;
    	}
    }

    public void removeIRSensorListener(IRSensorListener listener) {
    	synchronized (listeners) {
    		for (int i = 0; i < listenerSize; i++) {
    			if (listeners[i] == listener) {
    				listeners[i] = listeners[--listenerSize];
    				break;
    			}
    		}
    	}
    }

    private void counterIncremented(int val) {
    	synchronized (listeners) {
    		for (int i = listenerSize - 1; i >= 0; i--) { // Go backward so listeners can remove themselves from within the counterIncremeneted() without scrambling the array during this loop
    			listeners[i].counterIncremented(this, counter);
    		}
    	}
		
    	//System.out.println(name + ": INC (" + val + "," + direction + ")");
    }

    //
    // IScalarInputThresholdListener implementation
    //
    
    public void thresholdExceeded(IScalarInput pin, int val) {
    	//System.out.print(name);
    	//System.out.print(':');
    	//System.out.println(val);

    	boolean counterInc = false;
    	synchronized (this) {
    		if (val > threshLow && val < threshHigh) {
    			pin.enableThresholdEvents(true);
    			return;
    		}

    		if (isPinValueHigh(val) != isHigh) {
    			counter++;
    			isHigh = !isHigh;
    			setThresholds();
    			counterInc = true;
    		}

    	}

    	if (counterInc) counterIncremented(val);
    }

    public void thresholdChanged(IScalarInput pin, int low, int high) {}
   
    //
    // IRobotSensor implementation
    //
    
	public double getConvertedValue() {
		synchronized (this) {
			return counter;
		}
	}

	public long getRawValue() throws ResourceUnavailableException {
		try {
			return pin.getValue();
		} catch (IOException ioe) {
			throw new ResourceUnavailableException("Error while reading the IR sensor: " + ioe);
		}
	}

	public boolean testConnection() {
		try {
			return pin.getValue() != 0;
		} catch (IOException ioe) {
			return false;
		}
	}
	
	//
    // Private utility methods
	//
	
    private boolean isPinValueHigh(int val) {
    	return val > THRESHOLD;
    }
    
    private boolean isPinHigh() {
    	try {
    		return pin.getValue() > THRESHOLD;
    	} catch (IOException ioe) {
    		return false;
    	}
    }

    private void setThresholds() {
    	synchronized (this) {
    		if (isHigh) setThresholds(THRESHOLD, MAX_VALUE + 1);
    		else		setThresholds(MIN_VALUE - 1, THRESHOLD);
    	}
    }

    /**
     * Sets the thresholds on this IRSensor's pin to (low,high) (inclusive bounds). Accepts and handles bounds that are outside the range [MIN_VALUE,MAX_VALUE]
     * @param low the low threshold, inclusive
     * @param high the high threshold, inclusive
     */
    private void setThresholds(int low, int high) {
    	synchronized (this) {
    		threshHigh = high;
    		threshLow = low;
    		pin.setThresholds(Math.max(low, MIN_VALUE), Math.min(high, MAX_VALUE));
    		pin.enableThresholdEvents(true);
    	}
    }
}
